#include <climits>
#include <iostream>
#include <boost/lexical_cast.hpp>
#include <algorithm>
#include <map>

#include "fuzzyclassifier.h"

namespace fc
{
    
using namespace boost;
using namespace std;

bool cmpAttributesWeights(const AttributeWeight & w1, const AttributeWeight & w2)
{
    return w1.weight >= w2.weight;
}

FuzzyClassifier::FuzzyClassifier(int fuzzy_sets): FS(fuzzy_sets)
{

}


//--------------------------------------------------------------------------------------
//                  BUILDING PART
//--------------------------------------------------------------------------------------

void FuzzyClassifier::setTrainingData ( const vector< Example >& data, int attributes )
{
    attributes_ = attributes;
    cleanUp();
    createSets ( data );
    measureAttributes( data );
    createRules ( data );
}

void FuzzyClassifier::cleanUp()
{
    sets_.clear();
    sets_.resize ( attributes_, FuzzySets ( FS ) );
    rules_.clear();
    rules_.resize(attributes_);
}

void FuzzyClassifier::createSets ( const vector< Example >& data )
{
    updateExtremes ( data );
    for ( int attrNr = 0; attrNr < attributes_; ++attrNr )
    {
        double begin = min_.at ( attrNr );
        double interval =  max_.at ( attrNr ) - begin;
        double half = interval / ( FS - 1 );
        for ( int fsetNr = 0; fsetNr < FS; ++fsetNr )
        {
            sets_[attrNr][fsetNr].begin = begin + ( fsetNr - 1 ) * half;
            sets_[attrNr][fsetNr].end = begin + ( fsetNr + 1 ) * half;
        }
    }
}

void FuzzyClassifier::measureAttributes(const vector< Example > & data)
{
    attributesWeights_.resize(attributes_);
    for(int i = 0; i < attributes_; ++i)
        attributesWeights_[i] = AttributeWeight(i, measureAttribute(data, i));
    normalizeAttributesWeights();
    sort(attributesWeights_.begin(), attributesWeights_.end(), cmpAttributesWeights);
}

double FuzzyClassifier::measureAttribute(const vector< Example > & data, int attributeNr) const
{
    if(data.empty()) return 1.0;

    typedef pair<int, double> Value; // pair(class nr, weight)
    typedef map< int, Value > Map; // map(fset nr -> Value)

    Map results;
    for(vector<Example>::const_iterator it = data.begin(); it != data.end(); ++it)
    {
        pair<int, double> result = bestFitSet(it->antacedent[attributeNr], attributeNr);
        Map::iterator f = results.find(result.first);
        if(f == results.end())
            results.insert(make_pair(result.first, Value(it->consequent, result.second)));
        else if(f->second.second < result.second)
            f->second = Value(it->consequent, result.second);
    }

    int errors = 0;
    for(vector<Example>::const_iterator it = data.begin(); it != data.end(); ++it)
    {
        pair<int, double> result = bestFitSet(it->antacedent[attributeNr], attributeNr);
        Map::iterator f = results.find(result.first);
        if(f == results.end() || f->first != it->consequent)
            ++errors;
    }

    return (double)(data.size() - errors) / (double)data.size();
}

void FuzzyClassifier::normalizeAttributesWeights()
{
    double sum = 0;
    for(vector<AttributeWeight>::const_iterator it = attributesWeights_.begin(); it != attributesWeights_.end(); ++it)
        sum += it->weight;
    if(sum == 0) sum = 1;
    for(vector<AttributeWeight>::iterator it = attributesWeights_.begin(); it != attributesWeights_.end(); ++it)
        it->weight /= sum;
}

void FuzzyClassifier::updateExtremes ( const vector< Example >& data )
{
    min_.clear();
    max_.clear();
    min_.resize ( attributes_, INT_MAX );
    max_.resize ( attributes_, INT_MIN );

    for ( vector<Example>::const_iterator it = data.begin();
            it != data.end(); ++it )
    {
        for ( int attrNr = 0; attrNr < attributes_; ++attrNr )
        {
            if ( min_[attrNr] > it->antacedent[attrNr] )
                min_[attrNr] = it->antacedent[attrNr];
            if ( max_[attrNr] < it->antacedent[attrNr] )
                max_[attrNr] = it->antacedent[attrNr];
        }
    }
}


void FuzzyClassifier::createRules ( const vector< Example >& data )
{
    for( int attributes = attributes_; attributes > 0; --attributes)
        for ( vector<Example>::const_iterator exampleIt = data.begin();
                exampleIt != data.end(); ++exampleIt )
            putRule ( createRuleInfo ( *exampleIt, attributes ), attributes );
}

void FuzzyClassifier::putRule ( const RuleInfo& ruleInfo, int attributes )
{
    Rules::iterator ruleIt =  rules_[attributes-1].find ( ruleInfo.antacedent );
    if ( ruleIt == rules_[attributes-1].end() )
        rules_[attributes-1].insert ( make_pair ( ruleInfo.antacedent, Result ( ruleInfo.consequent, ruleInfo.weight ) ) );
        
    else if ( ruleIt->second.weight < ruleInfo.weight )
    {
        ruleIt->second.weight = ruleInfo.weight;
        ruleIt->second.resultClass = ruleInfo.consequent;
    }
}

string FuzzyClassifier::ruleToStr(RuleInfo rule) const
{
    string ruleStr = "";
    int fsNr = 0;
    for(string::const_iterator it = rule.antacedent.begin();
        it != rule.antacedent.end(); ++it, ++fsNr)
    {
        if(fsNr > 0) ruleStr += " ^ ";
        ruleStr += lexical_cast<string>((int)(*it));
    }
    ruleStr += " => " + lexical_cast<string>(rule.consequent) + " : " + lexical_cast<string>(rule.weight);
    return ruleStr;
}

RuleInfo FuzzyClassifier::createRuleInfo ( const Example& example, int attributes ) const
{
    RuleInfo ruleInfo ( "", example.consequent, 1 );
    for ( int attrId = 0; attrId < attributes; ++attrId )
    {
        int attrNr = attributesWeights_[attrId].attributeNr;
        int fset = 0;
        double membership = 0;
        for ( int fsetNr = 0; fsetNr < FS; ++fsetNr )
        {
            double membershipTmp = sets_[attrNr][fsetNr].membership ( example.antacedent[attrNr] );
            if ( membershipTmp > membership )
            {
                fset = fsetNr;
                membership = membershipTmp;
            }
        }
        ruleInfo.antacedent += ( char ) fset;
        ruleInfo.weight *= membership;
    }
    return ruleInfo;
}

//--------------------------------------------------------------------------------------
//                  CLASSIFYING PART
//--------------------------------------------------------------------------------------
Result FuzzyClassifier::classify ( const vector< double >& example ) const
{
    vector< pair< string, double > > keys = createKeys ( example );
    Result result ( 0, 0 );
    bool found = false;
    for( int attributes = attributes_; attributes > 0; --attributes)
    {
        for ( vector< pair< string, double > >::const_iterator it = keys.begin(); it != keys.end(); ++it )
        {
            Rules::const_iterator ruleIt = rules_[attributes-1].find ( antecedent(it->first, attributes) );
            if ( ruleIt == rules_[attributes-1].end() ) continue;
            double weight = measure ( ruleIt->second.weight, it->second );
            if ( result.weight < weight )
            {
                result = Result ( ruleIt->second.resultClass, weight );
                found = true;
            }
        }
        if(found) break;
    }
    return result;
}

string FuzzyClassifier::antecedent(string key, int attributes) const
{
    string antecedent = "";
    for(int i = 0; i < attributes; ++i)
        antecedent += key[attributesWeights_[i].attributeNr];
    return antecedent;
}

vector< pair< string, double > > FuzzyClassifier::createKeys ( const vector< double >& attributes ) const
{
    vector< pair< string, double > > keys;
    createKeysR ( attributes, 0, "", 1.0, keys );
    return keys;
}

void FuzzyClassifier::createKeysR ( const vector< double >& attributes,
                                    int position,
                                    const std::string& prefix,
                                    double currentWeight,
                                    vector< std::pair< string, double > >& keys ) const
{
    vector< pair<int, double> > foundSets = findSets ( attributes[position], position );
    if ( position == attributes.size() - 1 )
        for ( vector< pair<int, double> >::const_iterator it = foundSets.begin();
                it != foundSets.end(); ++it )
            keys.push_back ( make_pair ( prefix + ( char ) ( it->first ), currentWeight * it->second ) );
    else
        for ( vector< pair<int, double> >::const_iterator it = foundSets.begin();
                it != foundSets.end(); ++it )
            createKeysR ( attributes, position + 1, prefix + ( char ) ( it->first ), currentWeight * it->second, keys );
}

vector< pair<int, double> > FuzzyClassifier::findSets ( double value, int attributeNr ) const
{
    vector< pair<int, double> > result;
    double intersection = ( max_[attributeNr] - min_[attributeNr] ) / ( FS - 1 );
    double min = min_[attributeNr] - intersection;
    int position = ( value - min ) / intersection;
    if ( position <= 0 )
        result.push_back ( make_pair ( 0, 1.0 ) );
    else if ( position >= FS )
        result.push_back ( make_pair ( FS - 1, 1.0 ) );
    else
    {
        result.push_back ( make_pair ( position, sets_[attributeNr][position].membership ( value ) ) );
        result.push_back ( make_pair ( position - 1, sets_[attributeNr][position - 1].membership ( value ) ) );
    }
    return result;
}

pair<int, double> FuzzyClassifier::bestFitSet ( double value, int attributeNr ) const
{
    vector< pair<int, double> > sets = findSets(value, attributeNr);
    return (sets.size() == 1 ? sets[0] : sets[0].second >= sets[1].second ? sets[0] : sets[1]);
}

double FuzzyClassifier::measure ( double weight1, double weight2 ) const
{
    return ( weight1 + weight2 ) / 2;
}


}
